import re

from django.core.exceptions import ValidationError

CVSS2_REGEX = re.compile(r'(/?[A-Za-z]+:[A-Z]+)+')
CVSS2_METRICS_BASE = {
    'AV': {'L': 0.395, 'A': 0.646, 'N': 1.0},
    'AC': {'H': 0.35, 'M': 0.61, 'L': 0.71},
    'Au': {'M': 0.45, 'S': 0.56, 'N': 0.71},
    'C': {'N': 0, 'P': 0.275, 'C': 0.660},
    'I': {'N': 0, 'P': 0.275, 'C': 0.660},
    'A': {'N': 0, 'P': 0.275, 'C': 0.660},
}

CVSS2_METRICS_TEMPORAL = {
    'E': {'ND': 1, 'H': 1, 'F': 0.95, 'P': 0.90, 'U': 0.85},
    'RL': {'ND': 1, 'U': 1, 'W': 0.95, 'TF': 0.90, 'OF': 0.87},
    'RC': {'ND': 1, 'C': 1, 'UR': 0.95, 'UC': 0.90},
}

CVSS2_METRICS_ENVIRONMENTAL = {
    'CDP': {'ND': 0, 'N': 0, 'L': 0.1, 'LM': 0.3, 'MH': 0.4, 'H': 0.5},
    'TD': {'ND': 1, 'N': 0, 'L': 0.25, 'M': 0.75, 'H': 1},
    'CR': {'ND': 1, 'L': 0.5, 'M': 1, 'H': 1.51},
    'IR': {'ND': 1, 'L': 0.5, 'M': 1, 'H': 1.51},
    'AR': {'ND': 1, 'L': 0.5, 'M': 1, 'H': 1.51},
}
CVSS2_METRICS = CVSS2_METRICS_BASE | CVSS2_METRICS_TEMPORAL | CVSS2_METRICS_ENVIRONMENTAL
CVSS2_REQUIRED_METRICS = ['AV', 'AC', 'Au', 'C', 'I', 'A']


def parse_cvss2(vector):
    # Strip non-standardized prefix
    vector = (vector or '').replace('CVSS2#', '')

    if not vector or not CVSS2_REGEX.match(vector):
        raise ValidationError('Invalid CVSS2 vector: Invalid format')

    # parse CVSS metrics
    values = dict(map(lambda p: tuple(p.split(':')),
                  filter(None, vector.split('/'))))
    for k, v in values.items():
        if k not in CVSS2_METRICS or v not in CVSS2_METRICS[k]:
            raise ValidationError(
                f'Invalid CVSS2 vector: invalid metric value "{k}:{v}"')

    # Validate required metrics
    for m in CVSS2_REQUIRED_METRICS:
        if m not in values:
            raise ValidationError(
                f'Invalid CVSS2 vector: base metric "{m}" missing')

    return values


def is_cvss2(vector):
    try:
        parse_cvss2(vector)
        return True
    except ValidationError:
        return False


def calculate_score_cvss2(vector) -> dict | None:
    try:
        values = parse_cvss2(vector)
    except ValidationError:
        return None

    def has_metric_group(group):
        return any(map(lambda m: m in values and values[m] != 'X', group.keys()))

    def metric(name) -> float:
        m = CVSS2_METRICS.get(name, {}).get(values.get(name))
        if m is not None:
            return m
        return CVSS2_METRICS.get(name, {}).get('ND')

    def round_up(inp):
        return round(inp, ndigits=1)

    impact = 10.41*(1 -
                    (1 - metric('C')) *
                    (1 - metric('I')) *
                    (1 - metric('A')))
    adjusted_impact = min(10.41 * (1 - (
        (1 - metric('C') * metric('CR')) *
        (1 - metric('I') * metric('IR')) *
        (1 - metric('A') * metric('AR')))
    ), 10)
    exploitability = 20 * metric('AV') * metric('AC') * metric('Au')
    base_score = round_up(
        ((0.6 * impact) + (0.4 * exploitability) - 1.5) *
        (0 if impact == 0 else 1.176))
    temporal_score = round_up(
        base_score * metric('E') * metric('RL') * metric('RC'))

    environmental_score = round_up(
        ((0.6 * adjusted_impact) + (0.4 * exploitability) - 1.5) *
        (0 if adjusted_impact == 0 else 1.176))
    environmental_score = round_up(
        environmental_score * metric('E') * metric('RL') * metric('RC'))
    environmental_score = round_up(
        (environmental_score + (10 - environmental_score) * metric('CDP')) * metric('TD'))

    result = {
        "version": "2",
        "base": {
            "score": base_score,
            "exploitability": exploitability,
            "impact": impact,
        },
        "temporal": {
            "score": temporal_score,
            "exploitability": exploitability,
            "impact": impact,
        },
        "environmental": {
            "score": environmental_score,
            "exploitability": exploitability,
            "impact": adjusted_impact,
        },
    }
    if has_metric_group(CVSS2_METRICS_ENVIRONMENTAL):
        result["final"] = result["environmental"]
    elif has_metric_group(CVSS2_METRICS_TEMPORAL):
        result["final"] = result["temporal"]
    else:
        result["final"] = result["base"]
    return result
